package com.eeb.dropwizardmongo.example.resources;

import com.codahale.metrics.annotation.Timed;
import com.eeb.dropwizardmongo.example.api.Organization;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.mongojack.DBQuery;
import org.mongojack.DBUpdate;
import org.mongojack.JacksonDBCollection;

import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Map;

@Path("/organizations")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class OrganizationsResource {

    private JacksonDBCollection<Organization, String> organizations;

    public OrganizationsResource(JacksonDBCollection<Organization, String> organizations) {
        this.organizations = organizations;
    }

    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getOrgs(@QueryParam("admin") String admin,
                            @QueryParam("track") String track,
                            @QueryParam("user") String user) throws UnsupportedEncodingException {
        DBQuery.Query query = DBQuery.empty();
        if (admin != null)
            query = admin.startsWith("-") ?
                    query.nor(DBQuery.all("admins", admin.substring(1))) :
                    query.all("admins", admin);
        if (track != null)
            query = track.startsWith("-") ?
                    query.nor(DBQuery.all("tracks", URLEncoder.encode(track.substring(1), "UTF-8"))) :
                    query.all("tracks", URLEncoder.encode(track, "UTF-8"));
        if (user != null)
            query = user.startsWith("-") ?
                    query.nor(DBQuery.all("users", user.substring(1))) :
                    query.all("users", user);
        return Response.ok(organizations.find(query).toArray()).build();
    }

    @Path("/{organization}")
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getOrg(@PathParam("organization") String organization) {
        return Response.ok(organizations.findOne(DBQuery.is("name", organization))).build();
    }

    @Path("/{organization}/tracks")
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getTracks(@PathParam("organization") String organization) {
        return Response.ok(organizations.findOne(DBQuery.is("name", organization)).getTracks()).build();
    }

    @Path("/{organization}/users")
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getUsers(@PathParam("organization") String organization) {
        return Response.ok(organizations.findOne(DBQuery.is("name", organization)).getUsers()).build();
    }

    @POST
    @Timed
    public Response postOrg(@Valid Organization org) throws URISyntaxException, UnsupportedEncodingException {
        URI uri = new URI("/" + org.getName());
        if (organizations.findOne(DBQuery.is("name", org.getName())) == null)
            organizations.insert(org);
        return Response.created(uri).entity(organizations.findOne(DBQuery.is("name", org.getName()))).build();
    }

    @PUT
    @Timed
    public Response putOrg(@Valid Organization organization) {
        return Response.status(405).entity("PUT to /organizations/{name} or POST to /organizations").build();
    }

    @Path("/{organization}")
    @PUT
    @Timed
    public Response putOrg(@PathParam("organization") String organization,
                           @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("name", organization);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {  });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() != null) {
                organizations.update(query, DBUpdate.set(entry.getKey(), entry.getValue()));
            }
        }
        Organization putOrg = organizations.findOne(DBQuery.is("name", organization));
        return Response.ok(putOrg).build();
    }

    @Path("/{organization}/admins")
    @PUT
    @Timed
    public Response putAdmin(@PathParam("organization") String organization,
                             @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("name", organization);
        Organization org = organizations.findOne(query);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {  });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            org.addUser((String) entry.getValue());
        }
        DBUpdate.Builder builder = DBUpdate.set("admins", org.getUsers());
        organizations.update(query, builder);
        return Response.noContent().build();
    }

    @Path("/{organization}/tracks")
    @PUT
    @Timed
    public Response putTrack(@PathParam("organization") String organization,
                             @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("name", organization);
        Organization org = organizations.findOne(query);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {  });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            org.addUser((String) entry.getValue());
        }
        DBUpdate.Builder builder = DBUpdate.set("tracks", org.getUsers());
        organizations.update(query, builder);
        return Response.noContent().build();
    }

    @Path("/{organization}/users")
    @PUT
    @Timed
    public Response putUser(@PathParam("organization") String organization,
                            @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("name", organization);
        Organization org = organizations.findOne(query);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {  });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            org.addUser((String) entry.getValue());
        }
        DBUpdate.Builder builder = DBUpdate.set("users", org.getUsers());
        organizations.update(query, builder);
        return Response.noContent().build();
    }

    @Path("/{organization}")
    @DELETE
    @Timed
    public Response deleteOrg(@PathParam("organization") String organization) {
        organizations.findAndRemove(DBQuery.is("name", organization));
        return Response.ok(organizations.find().toArray()).build();
    }

    @Path("/{organization}/track/{track}")
    @DELETE
    @Timed
    public Response deleteTrack(@PathParam("organization") String organization,
                                @PathParam("track") String track) {
        DBQuery.Query orgQuery = DBQuery.is("name", organization);
        organizations.findOne(orgQuery).removeTrack(track);
        return Response.ok(organizations.findOne(orgQuery).getTracks()).build();
    }

    @Path("/{organization}/user/{user}")
    @DELETE
    @Timed
    public Response deleteUser(@PathParam("organization") String organization,
                               @PathParam("user") String userName) {
        DBQuery.Query orgQuery = DBQuery.is("name", organization);
        organizations.findOne(orgQuery).removeUser(userName);
        return Response.ok(organizations.findOne(orgQuery).getUsers()).build();
    }

}